package com.weijian.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.weijian.common.ApiResponse;
import com.weijian.common.BusinessException;
import com.weijian.dal.entity.UserSignIn;
import com.weijian.dal.entity.UserSignInStats;
import com.weijian.dal.mapper.UserSignInMapper;
import com.weijian.dal.mapper.UserSignInStatsMapper;
import com.weijian.dal.dto.signin.SignInMonthlyRecord;
import com.weijian.service.SignInService;
import com.weijian.util.RedisKeyUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.YearMonth;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * 用户签到服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SignInServiceImpl extends ServiceImpl<UserSignInMapper, UserSignIn> implements SignInService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final UserSignInStatsMapper userSignInStatsMapper;
    private final UserSignInMapper userSignInMapper;
    private final StringRedisTemplate stringRedisTemplate;

    // 签到Lua脚本，保证原子性（去除注释和换行符版本）
    private static final String SIGN_IN_LUA_SCRIPT =
            "local key = KEYS[1] " +
                    "local dayOfYear = tonumber(ARGV[1]) " +
                    "local userId = ARGV[2] " +
                    "if not dayOfYear or dayOfYear <= 0 or dayOfYear > 366 then " +
                    "    return nil " +
                    "end " +
                    "local alreadySigned = redis.call('GETBIT', key, dayOfYear - 1) " +
                    "if alreadySigned == 1 then " +
                    "    return 0 " +
                    "end " +
                    "redis.call('SETBIT', key, dayOfYear - 1, 1) " +
                    "local continuousDays = 0 " +
                    "for i = dayOfYear - 2, 0, -1 do " +
                    "    local bit = redis.call('GETBIT', key, i) " +
                    "    if bit == 1 then " +
                    "        continuousDays = continuousDays + 1 " +
                    "    else " +
                    "        break " +
                    "    end " +
                    "end " +
                    "return continuousDays + 1";


    /**
     * 用户每日签到 - 使用Redis Bitmap + Lua脚本保证原子性，防重复签到
     */
    @Override
    public ApiResponse<UserSignIn> dailySignIn(Long userId) {
        try {
            LocalDate today = LocalDate.now();
            int dayOfYear = today.getDayOfYear();
            int year = today.getYear();

            String redisKey = RedisKeyUtil.getUserSignInKey(userId, year);

            log.info("准备执行签到: userId={}, year={}, dayOfYear={}, redisKey={}",
                    userId, year, dayOfYear, redisKey);

            // 执行Lua脚本进行签到
            DefaultRedisScript<Long> script = new DefaultRedisScript<>(SIGN_IN_LUA_SCRIPT, Long.class);
            log.info("Lua脚本内容: {}", SIGN_IN_LUA_SCRIPT);

            Long continuousDays = stringRedisTemplate.execute(
                    script,
                    Collections.singletonList(redisKey),
                    String.valueOf(dayOfYear),
                    String.valueOf(userId)
            );

            log.info("Redis脚本执行结果: continuousDays={}", continuousDays);

            // 检查Redis脚本执行结果
            if (continuousDays == null) {
                log.error("签到失败：Redis脚本执行返回null，userId={}, dayOfYear={}, redisKey={}",
                        userId, dayOfYear, redisKey);
                // 添加Redis连接状态检查
                try {
                    String redisPing = redisTemplate.getConnectionFactory().getConnection().ping();
                    log.info("Redis连接状态: {}", redisPing);
                } catch (Exception e) {
                    log.error("Redis连接检查失败", e);
                }
                throw new BusinessException("签到服务暂时不可用，请稍后重试");
            }

            if (continuousDays == 0) {
                throw new BusinessException("今日已签到，请勿重复签到");
            }

            // 保存签到记录到数据库
            UserSignIn signInRecord = new UserSignIn();
            signInRecord.setUserId(userId);
            signInRecord.setSignInDate(today);
            signInRecord.setConsecutiveDays(continuousDays.intValue());
            signInRecord.setRewardPoints(1); // 固定奖励1积分
            signInRecord.setRewardExp(1); // 固定奖励1经验值

            try {
                userSignInMapper.insert(signInRecord);
            } catch (Exception dbException) {
                log.error("数据库插入签到记录失败，启动补偿机制: userId={}", userId, dbException);
                // 启动补偿机制
                startCompensationTask(userId, today, continuousDays.intValue());
                // 数据库操作失败不影响用户签到体验，仍然返回成功
            }

            // 更新签到统计
            try {
                updateUserSignInStats(userId, today, continuousDays.intValue());
            } catch (Exception statsException) {
                log.error("更新签到统计失败，启动补偿机制: userId={}", userId, statsException);
                // 启动补偿机制
                startStatsCompensationTask(userId, today, continuousDays.intValue());
            }

            log.info("用户签到成功：userId={}, date={}, continuousDays={}", userId, today, continuousDays);

            return ApiResponse.success(signInRecord);

        } catch (BusinessException e) {
            log.warn("签到失败：userId={}, reason={}", userId, e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("签到异常：userId={}", userId, e);
            throw new BusinessException("签到失败，请稍后重试");
        }
    }

    /**
     * 启动签到记录补偿任务
     */
    private void startCompensationTask(Long userId, LocalDate signDate, int consecutiveDays) {
        // 使用线程池异步执行补偿任务
        CompletableFuture.runAsync(() -> {
            int maxRetries = 3;
            for (int i = 0; i < maxRetries; i++) {
                try {
                    Thread.sleep(1000 * (i + 1)); // 递增延迟重试

                    UserSignIn signInRecord = new UserSignIn();
                    signInRecord.setUserId(userId);
                    signInRecord.setSignInDate(signDate);
                    signInRecord.setConsecutiveDays(consecutiveDays);
                    signInRecord.setRewardPoints(1);
                    signInRecord.setRewardExp(1);

                    userSignInMapper.insert(signInRecord);
                    log.info("签到记录补偿成功: userId={}, date={}", userId, signDate);
                    return;
                } catch (Exception retryException) {
                    log.warn("签到记录补偿第{}次尝试失败: userId={}, error={}", i + 1, userId, retryException.getMessage());
                }
            }
            log.error("签到记录补偿最终失败: userId={}", userId);
        });
    }

    /**
     * 启动签到统计补偿任务
     */
    private void startStatsCompensationTask(Long userId, LocalDate signDate, int consecutiveDays) {
        // 使用线程池异步执行补偿任务
        CompletableFuture.runAsync(() -> {
            int maxRetries = 3;
            for (int i = 0; i < maxRetries; i++) {
                try {
                    Thread.sleep(1000 * (i + 1)); // 递增延迟重试
                    updateUserSignInStats(userId, signDate, consecutiveDays);
                    log.info("签到统计补偿成功: userId={}", userId);
                    return;
                } catch (Exception retryException) {
                    log.warn("签到统计补偿第{}次尝试失败: userId={}, error={}", i + 1, userId, retryException.getMessage());
                }
            }
            log.error("签到统计补偿最终失败: userId={}", userId);
        });
    }


    /**
     * 查询用户今日签到状态 - 优先Redis，失败时降级到数据库查询
     */
    @Override
    public ApiResponse<Boolean> getTodaySignStatus(Long userId) {
        try {
            LocalDate today = LocalDate.now();
            int dayOfYear = today.getDayOfYear();
            int year = today.getYear();

            String redisKey = RedisKeyUtil.getUserSignInKey(userId, year);
            Boolean isSigned = redisTemplate.opsForValue().getBit(redisKey, dayOfYear - 1);

            return ApiResponse.success(isSigned != null && isSigned);

        } catch (Exception e) {
            log.error("查询签到状态异常：userId={}", userId, e);
            // Redis查询失败时，降级到数据库查询
            return getTodaySignStatusFromDB(userId);
        }
    }

    /**
     * 获取用户签到统计信息 - 包含总签到天数、连续签到等数据
     */
    @Override
    public ApiResponse<UserSignInStats> getUserSignInStats(Long userId) {
        try {
            UserSignInStats stats = userSignInStatsMapper.selectById(userId);
            if (stats == null) {
                // 初始化用户签到统计
                stats = initializeUserSignInStats(userId);
            }
            return ApiResponse.success(stats);
        } catch (Exception e) {
            log.error("获取签到统计异常：userId={}", userId, e);
            throw new BusinessException("获取签到统计失败");
        }
    }

    @Override
    public ApiResponse<SignInMonthlyRecord> getMonthlySignInRecord(Long userId, int year, int month) {
        try {
            YearMonth yearMonth = YearMonth.of(year, month);
            LocalDate startDate = yearMonth.atDay(1);
            LocalDate endDate = yearMonth.atEndOfMonth();

            LambdaQueryWrapper<UserSignIn> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserSignIn::getUserId, userId)
                    .between(UserSignIn::getSignInDate, startDate, endDate)
                    .orderByAsc(UserSignIn::getSignInDate);

            List<UserSignIn> signInList = userSignInMapper.selectList(queryWrapper);

            SignInMonthlyRecord monthlyRecord = new SignInMonthlyRecord();
            monthlyRecord.setUserId(userId);
            monthlyRecord.setYear(year);
            monthlyRecord.setMonth(month);
            monthlyRecord.setSignInDays(signInList.stream()
                    .map(UserSignIn::getSignInDate)
                    .collect(Collectors.toList()));
            monthlyRecord.setTotalDays(signInList.size());

            return ApiResponse.success(monthlyRecord);

        } catch (Exception e) {
            log.error("获取月度签到记录异常：userId={}, year={}, month={}", userId, year, month, e);
            throw new BusinessException("获取签到记录失败");
        }
    }

    /**
     * 更新用户签到统计
     */
    private void updateUserSignInStats(Long userId, LocalDate signDate, int continuousDays) {
        UserSignInStats stats = userSignInStatsMapper.selectById(userId);
        if (stats == null) {
            stats = new UserSignInStats();
            stats.setUserId(userId);
            stats.setTotalSignDays(1);
            stats.setCurrentContinuousDays(continuousDays);
            stats.setMaxContinuousDays(continuousDays);
            stats.setLastSignDate(signDate);
            stats.setTotalRewardPoints(1);
            stats.setTotalRewardExp(1);
            userSignInStatsMapper.insert(stats);
        } else {
            stats.setTotalSignDays(stats.getTotalSignDays() + 1);
            stats.setCurrentContinuousDays(continuousDays);
            stats.setMaxContinuousDays(Math.max(stats.getMaxContinuousDays(), continuousDays));
            stats.setLastSignDate(signDate);
            stats.setTotalRewardPoints(stats.getTotalRewardPoints() + 1);
            stats.setTotalRewardExp(stats.getTotalRewardExp() + 1);
            userSignInStatsMapper.updateById(stats);
        }
    }

    /**
     * 初始化用户签到统计
     */
    private UserSignInStats initializeUserSignInStats(Long userId) {
        UserSignInStats stats = new UserSignInStats();
        stats.setUserId(userId);
        stats.setTotalSignDays(0);
        stats.setCurrentContinuousDays(0);
        stats.setMaxContinuousDays(0);
        stats.setTotalRewardPoints(0);
        stats.setTotalRewardExp(0);
        userSignInStatsMapper.insert(stats);
        return stats;
    }

    /**
     * 从数据库查询今日签到状态 - Redis查询失败时的降级方案
     */
    private ApiResponse<Boolean> getTodaySignStatusFromDB(Long userId) {
        try {
            LambdaQueryWrapper<UserSignIn> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserSignIn::getUserId, userId)
                    .eq(UserSignIn::getSignInDate, LocalDate.now())
                    .last("LIMIT 1");

            UserSignIn signIn = userSignInMapper.selectOne(queryWrapper);
            return ApiResponse.success(signIn != null);

        } catch (Exception e) {
            log.error("数据库查询签到状态异常：userId={}", userId, e);
            return ApiResponse.success(false);
        }
    }
}